package edu.umd.ion.wavemeter.server;

import java.util.Vector;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import edu.umd.ion.wavemeter.service.ChannelEvent;
import edu.umd.ion.wavemeter.service.ChannelRoutingInfo;
import edu.umd.ion.wavemeter.service.ServerParameters;
import edu.umd.ion.wavemeter.service.WavemeterData;
import edu.umd.ion.wavemeter.service.WavemeterException;
import edu.umd.ion.wavemeter.service.WavemeterSetException;

public class CustomChannelManager implements ChannelManager {
	private PollingThreadInterface WavemeterDevice;
	private WavemeterData Data;
	private ExposureControl MyExposureControl;
	private FiberSwitcher Switcher = new FiberSwitcher("FPGA_Fiberswitch");

	private final ScheduledExecutorService Scheduler =
	       Executors.newSingleThreadScheduledExecutor();
	private ScheduledFuture<?> SwitcherHandle;
	private ScheduledFuture<?> TriggerHandle;
	private ScheduledFuture<?> WatchdogHandle;
	
	/**
	 *  OperationMode
	 *  0: Switch between all enabled virtual channels
	 *  1: Lock to rawchannel, cycle through all virtual channels of one rawchannel
	 *  2: Lock to virtchannel, only measure this one virtchannel
	 */
	private int OperationMode = 1; 
	private ChannelId LockToChannel = new ChannelId(0);

	private final int MaxVirtChannels = WavemeterConfiguration.NumVirtChannels();
	private int NumUsedVirtChannels = 0;
	private int NumUsedSwitcherVirtChannels = 0;
	private Integer[] ActiveSwitcherVirtChannels = new Integer[MaxVirtChannels];
	private Boolean[] VirtChannelList = new Boolean[MaxVirtChannels];
	private long UnderexposedTimeout = 30000;
	private boolean CalibrationLockEngaged=false;
	private VirtualChannelData[] VirtualData;
	private int CurrentChannelPointer=0;
	
	/** 
	 *  Cache of the Wavemeters Active Channel
	 */
	int CurrentActiveChannel = 0; 
	
	int CurrentSwitcherPosition = 0;
	int LastSwitcherPosition = 0;
	
	private class VirtualChannelData {
		RunnableOne<Integer> Lock, Unlock;
		boolean LockedChannel;
		long TimeoutMillis;
	}
	
	private class ChannelId {
		private int Virtual;
		public ChannelId(int virtual) { Virtual=virtual; }
		public ChannelId(ChannelId id) { Virtual=id.Virtual; }
		public int getOffset() { return Virtual==0?0:Virtual-1; }
		public int getVirtual() { return Virtual; }
		public int getPort() { return Virtual==0?0:1; }
		public void setVirtual(int virtual) { Virtual=virtual; }
	};
	
	/** 
	 *  Queue of virtual channels being measured
	 */
	ChannelId LastChannel, CurrentChannel, NextChannel;

	private class TriggerMeasurement implements Runnable {
		@Override
		public void run() {
			WavemeterLibrary.TriggerMeasurement(WavemeterLibrary.cCtrlMeasurementTriggerSuccess);
			scheduleWatchdog(200);
			//System.out.println("Measurement triggered");
		}		
	};
	
	private boolean scheduleTriggerMeasurement(int delay) {
		if (TriggerHandle==null || TriggerHandle.isDone() ) {
			TriggerHandle = Scheduler.schedule(new TriggerMeasurement(), delay, TimeUnit.MILLISECONDS);
			return true;
		} else {
			System.out.println("TriggerMeasurement already scheduled");
		}
		return false;
	}
	
	private class SwitchChannel implements Runnable {
		private ChannelId Channel;
		public SwitchChannel(ChannelId channel) {
			Channel = channel;
		}
		@Override
		public void run() {
			int RawChannelSwitchDelay = 0;
			if (Channel.getPort()==0) {
				if (CurrentActiveChannel!=0) {
					int val = WavemeterLibrary.SetActiveChannel(3, 1, 1, 0);
					System.out.println("Switching to port 1: "+val);
					CurrentActiveChannel = 0;
					RawChannelSwitchDelay = 2000;
				}			
			} else {
				LastSwitcherPosition = CurrentSwitcherPosition;
				if (CurrentActiveChannel==0) {
					int val = WavemeterLibrary.SetActiveChannel(3,1,2,0);
					System.out.println("Switching to port 2: "+val);
					CurrentActiveChannel = 1;
					RawChannelSwitchDelay = 2000;
				}
				CurrentSwitcherPosition = Channel.getVirtual();
				Switcher.setChannel(CurrentSwitcherPosition-1);
			}
			scheduleTriggerMeasurement(200+RawChannelSwitchDelay);
		}		
	};
	
	private boolean scheduleSwitchChannel(ChannelId channel, int delay) {
		if (SwitcherHandle==null || SwitcherHandle.isDone() ) {
			SwitcherHandle = Scheduler.schedule(new SwitchChannel(channel), delay, TimeUnit.MILLISECONDS);
			return true;
		} else
			System.out.println("SwitchChannel already scheduled");
		return false;
	}
	
	private class Watchdog implements Runnable {
		@Override
		public void run() {
			System.out.println("Watchdog triggered measurement");
			scheduleTriggerMeasurement(100);
		}		
	};
	
	private boolean scheduleWatchdog(int delay) {
		if (WatchdogHandle==null || WatchdogHandle.isDone() ) {
			WatchdogHandle = Scheduler.schedule(new Watchdog(), delay, TimeUnit.MILLISECONDS);
			return true;
		} else
			System.out.println("SwitchChannel already scheduled");
		return false;
		
	}
	
	public CustomChannelManager(PollingThreadInterface Device, WavemeterData data, ExposureControl c) {
		WavemeterDevice = Device;
		Data = data;
		MyExposureControl = c;
		VirtualData = new VirtualChannelData[MaxVirtChannels];
		for (int i=0; i<MaxVirtChannels; ++i) {
			VirtChannelList[i] = false;
			VirtualData[i] = new VirtualChannelData();
		}
		WavemeterLibrary.TriggerMeasurement(WavemeterLibrary.cCtrlMeasurementInterrupt);
		//ChannelsCount = WavemeterLibrary.GetChannelsCount(0);
	}
	
	@Override
	public int activeChannels() {
		return NumUsedVirtChannels;
	}

	@Override
	public int activeVirtChannel(int rawchannel) {
		return rawchannel==0? 0 : LastSwitcherPosition; // CurrentVirtChannel;
	}

	@Override
	public int calibrationChannelLock(int virtchannel)
			throws WavemeterSetException {
		WavemeterDevice.setEnable(false);
		Switcher.setChannel(virtchannel-1);
		if (SwitcherHandle!=null) SwitcherHandle.cancel(true);
		if (TriggerHandle!=null) TriggerHandle.cancel(true);
		if (WatchdogHandle!=null) WatchdogHandle.cancel(true);
		CalibrationLockEngaged = true;
		return virtchannel==0?0:1;
	}

	@Override
	public void calibrationChannelUnlock() throws WavemeterSetException {
		CalibrationLockEngaged = false;
		WavemeterDevice.setEnable(true);		
		scheduleChannel(0);
	}

	@Override
	public Vector<ChannelRoutingInfo> channelRouting() {
		Vector<ChannelRoutingInfo> Routing = new Vector<ChannelRoutingInfo>();
		for (int j=0; j!=MaxVirtChannels; ++j) {
			if (VirtChannelList[j]) {
				ChannelRoutingInfo ChannelInfo = new ChannelRoutingInfo();
				ChannelInfo.RawChannel = j==0?0:1;
				ChannelInfo.VirtChannel = j;
				ChannelInfo.Exposure = Math.max(Data.ChannelData[ChannelInfo.VirtChannel].Exposure1, 
						Data.ChannelData[ChannelInfo.VirtChannel].Exposure2);
				Routing.add(ChannelInfo);
			}
		}
		return Routing;
	}

	@Override
	public void cleanup() {
		Scheduler.shutdown();
		Switcher.close();
	}

	@Override
	public int lastVirtChannel(int rawchannel) {
		return rawchannel==0? 0 : LastSwitcherPosition;
	}

	@Override
	public void obtainedResult(int virtchannel, boolean good)
			throws WavemeterException {
		if (WatchdogHandle!=null) WatchdogHandle.cancel(true);
		if (good) {
			VirtualData[virtchannel].TimeoutMillis = Long.MAX_VALUE; 
			return;
		}
		if (VirtualData[virtchannel].TimeoutMillis==Long.MAX_VALUE) {
			VirtualData[virtchannel].TimeoutMillis = System.currentTimeMillis()+UnderexposedTimeout;
		}		
		if (System.currentTimeMillis()>VirtualData[virtchannel].TimeoutMillis) {
			System.out.println(System.currentTimeMillis()+" > "+VirtualData[virtchannel].TimeoutMillis);
			setChannelActive(virtchannel,false);
		}
	}

	@Override
	public void pushInterlock(int virtchannel, boolean engage) {
	}

	@Override
	public void registerRawChannelCallback(int rawchannel,
			RunnableOne<Integer> lock, RunnableOne<Integer> unlock) {
		VirtualData[rawchannel].Lock = lock; 
		VirtualData[rawchannel].Unlock = unlock;
	}

	@Override
	public int scheduleChannel(int rawchannel)  {
		if (!CalibrationLockEngaged && NumUsedVirtChannels>0) {
			LastChannel = CurrentChannel;
			CurrentChannel = NextChannel;
			if (OperationMode==0) /* switching mode */ {
				if (CurrentChannelPointer+1==NumUsedSwitcherVirtChannels && VirtChannelList[0]) {
					CurrentChannelPointer=-1;
					// Schedule Raw Channel 0  next
					NextChannel = new ChannelId(0);
					WavemeterDevice.notifyClients();
				} else {
					// Schedule Raw Channel 1 next
					CurrentChannelPointer = (CurrentChannelPointer+1) % NumUsedSwitcherVirtChannels;
					NextChannel = new ChannelId(ActiveSwitcherVirtChannels[CurrentChannelPointer]);
				}
			} else if (OperationMode==1) /* Lock to raw channel */ {
				if (LockToChannel.getPort()==0) {
					NextChannel = new ChannelId(0);
					WavemeterDevice.notifyClients();
				} else {
					CurrentChannelPointer = (CurrentChannelPointer+1) % NumUsedSwitcherVirtChannels;
					NextChannel = new ChannelId(ActiveSwitcherVirtChannels[CurrentChannelPointer]);
					if (CurrentChannelPointer==0) WavemeterDevice.notifyClients();
				}
			} else /* lock to virtual channel */ {
				NextChannel = new ChannelId(LockToChannel);
				WavemeterDevice.notifyClients();
			}
			MyExposureControl.pushExposure(NextChannel.getPort(), NextChannel.getVirtual());
			scheduleSwitchChannel(new ChannelId(CurrentChannel),200);
		}
		return CurrentChannel.getVirtual();
	}
	
	@Override
	public void setChannelActive(int virtchannel, boolean active)
	throws WavemeterException {
		VirtChannelList[virtchannel] = active;
		Data.ChannelData[virtchannel].active = active;
		reroute();
		WavemeterDevice.setEnable(NumUsedVirtChannels>0);
		if (active) {
			VirtualData[virtchannel].TimeoutMillis = Long.MAX_VALUE;
			//scheduleChannel(0);
		}
		WavemeterDevice.notifyClients();
		if (NumUsedVirtChannels==1 && active) {
			LockToChannel.setVirtual(virtchannel);
			if (LastChannel==null) { LastChannel = new ChannelId(virtchannel); }
			if (CurrentChannel==null) { CurrentChannel = new ChannelId(virtchannel);}
			if (NextChannel==null) { NextChannel = new ChannelId(virtchannel); }
			scheduleChannel(0);
			//triggerMeasurement(150); //apparently needed for startup
		}
	}

	private void reroute() {
		NumUsedSwitcherVirtChannels = 0;
		for (int i=1; i<MaxVirtChannels; ++i) {
			if (VirtChannelList[i]) {
				ActiveSwitcherVirtChannels[NumUsedSwitcherVirtChannels]=i;
				++NumUsedSwitcherVirtChannels;
			}
		}
		NumUsedVirtChannels = NumUsedSwitcherVirtChannels + (VirtChannelList[0]?1:0);
	}
	
	@Override
	public void setChannelLocked(int virtchannel, boolean locked)
			throws WavemeterException {
	}

	@Override
	public void setRawChannelState(int rawchannel, boolean active) {
	}

	@Override
	public void setParameters(ServerParameters param) {
		MyExposureControl.setParameters(param);
		param.UnderexposedTimeout = Math.max(1000, param.UnderexposedTimeout);
		UnderexposedTimeout = param.UnderexposedTimeout;
	}

	@Override
	public ServerParameters getParameters() {
		ServerParameters P = new ServerParameters();
		P.UnderexposedTimeout = UnderexposedTimeout;
		return P;
	}

	@Override
	public void raiseChannelEvent(ChannelEvent E) {
		LockToChannel.setVirtual( E.getVirtChannel() );
		OperationMode = E.isControlDown() ? 2 : E.isAltDown() ? 0 : 1;
		System.out.println("Set OperationMode "+OperationMode+" LockToChannel "+LockToChannel.getVirtual());
		if (E.isAltDown()) System.out.print("Alt ");
		if (E.isControlDown()) System.out.print("Control ");
		if (E.isShiftDown()) System.out.print("Shift ");
		System.out.println(" X: "+E.getXMovement()+" Y: "+E.getYMovement());
	}

}
